package main

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"time"

	"gopkg.in/mgo.v2/bson"
)

type Processor struct {
	dbo          *DBOperation
	cfg          *Config
	_idGps       bson.ObjectId
	_idTire      bson.ObjectId
	lastTireData *LastData
}

func NewProcessor(dbo *DBOperation, cfg *Config) *Processor {
	ltd := NewLastData(cfg.Tire.WarningRepeatTimes, cfg.Tire.ErrorPressureRepeatTimes)
	p := Processor{dbo: dbo, cfg: cfg, lastTireData: ltd}
	return &p
}

func (p *Processor) Run() {

	if p.cfg.Gps.Active {
		if bson.IsObjectIdHex(p.cfg.Gps.Id) {
			p._idGps = bson.ObjectIdHex(p.cfg.Gps.Id)
		}
		go p.processPosition()
	}

	if p.cfg.Tire.Active {
		if bson.IsObjectIdHex(p.cfg.Tire.Id) {
			p._idTire = bson.ObjectIdHex(p.cfg.Tire.Id)
		}
		go p.processTire()
	}
}

func (p *Processor) processPosition() {
	zLog("启动读GPS数据协程")
	defer zLog("读GPS数据协程退出")
	var rids []string
	var res []GPSDataT

	for {
		if len(p.cfg.Rids) > 0 {

			rids = p.cfg.Rids
		} else if len(p.cfg.TeamIds) > 0 {

			rids = p.dbo.teamReceivers(p.cfg.TeamIds)
		} else {

			rids = p.dbo.companyReceivers(p.cfg.CompanyId)
		}
		if len(rids) == 0 {

			time.Sleep(time.Second * 60 * 30)
			continue
		}

		res = p.dbo.newPosition(rids, p._idGps, p.cfg.Gps.MaxRecord)
		if res == nil {
			time.Sleep(time.Second * time.Duration(p.cfg.Gps.Interval))
		} else {
			zLog("读到", len(res), "条新的GPS数据")

			sendDatas := make([]GPSDataSendT, 0)
			for _, v := range res {
				if v.Date > 0 && v.Time > 0 {
					dataSend := GPSDataSendT{}
					dataSend.fromData(v)
					dataSend.Type = p.dbo.getRidType(dataSend.SN)
					sendDatas = append(sendDatas, dataSend)
				}
			}
			/*
				sendDatas := make([]GPSDataSendT, len(res))
				for k, v := range res {
					sendDatas[k].fromData(v)
					sendDatas[k].Type = p.dbo.getRidType(sendDatas[k].SN)
				}
			*/
			p._idGps = res[len(res)-1].Id

			err := p.Post(p.cfg.Gps.Host, p._idGps, sendDatas)
			if err != nil {
				zLog(p.cfg.CompanyName, "服务器GPS数据推送异常:", err.Error())

				switch p.cfg.Failed.Action {
				case 0:

				case 1:

					zLog(p.cfg.CompanyName, "服务器异常,GPS数据推送服务停止:", err.Error())
					return
				case 2:

					for i := 1; i < p.cfg.Failed.RetryTimes; i++ {

						time.Sleep(time.Duration(p.cfg.Failed.RetryInterval) * time.Second)
						zLog(p.cfg.CompanyName, "服务器异常,准备第", i, "次重新发送:", p._idGps)
						err = p.Post(p.cfg.Gps.Host, p._idGps, sendDatas)
						if err == nil {

							break
						}
					}
					if err != nil {
						zLog(p.cfg.CompanyName, "服务器异常，", p.cfg.Failed.RetryTimes, "次发送都失败，GPS数据推送服务停止")
						return
					}
				}
			}

			p.cfg.Gps.Id = p._idGps.Hex()
			p.cfg.Save()
			time.Sleep(time.Second * 10)
		}
	}
}

func (p *Processor) processTire() {
	zLog("启动读胎温胎压数据协程")
	defer zLog("读胎温胎压数据协程退出")
	var rids []string
	var res []TireDataRawT

	for {
		if len(p.cfg.Rids) > 0 {

			rids = p.cfg.Rids
		} else if len(p.cfg.TeamIds) > 0 {

			rids = p.dbo.teamReceivers(p.cfg.TeamIds)
		} else {

			rids = p.dbo.companyReceivers(p.cfg.CompanyId)
		}
		if len(rids) == 0 {

			time.Sleep(time.Second * 60 * 30)
			continue
		}

		res = p.dbo.newTire(rids, p._idTire, p.cfg.Tire.MaxRecord)
		if res == nil {
			time.Sleep(time.Second * time.Duration(p.cfg.Tire.Interval))
		} else {
			zLog("读到", len(res), "条新的胎温胎压数据")
			sendDatas := make([]TireDataSendT, len(res))
			for k, v := range res {
				sendDatas[k].fromTireRawData(&v, p.cfg.Tire.NeedSensorLost, p.cfg.Tire.FixData,
					p.cfg.Tire.MaxTemperature, p.cfg.Tire.ErrorPressureRange, p.lastTireData)
				sendDatas[k].Type = p.dbo.getRidType(sendDatas[k].SN)
			}
			p._idTire = res[len(res)-1].Id

			err := p.Post(p.cfg.Tire.Host, p._idTire, sendDatas)
			if err != nil {
				zLog(p.cfg.CompanyName, "服务器胎温胎压数据推送异常：", err.Error())

				switch p.cfg.Failed.Action {
				case 0:

				case 1:

					zLog(p.cfg.CompanyName, "服务器异常，胎温胎压推送服务停止：", err.Error())
					return
				case 2:

					for i := 1; i < p.cfg.Failed.RetryTimes; i++ {

						time.Sleep(time.Duration(p.cfg.Failed.RetryInterval) * time.Second)
						zLog(p.cfg.CompanyName, "服务器异常，准备第", i, "次重新发送:", p._idTire)
						err = p.Post(p.cfg.Tire.Host, p._idTire, sendDatas)
						if err == nil {

							break
						}
					}
					if err != nil {
						zLog(p.cfg.CompanyName, "服务器异常，", p.cfg.Failed.RetryTimes, "次发送都失败，胎温胎压推送服务停止")
						return
					}
				}
			}

			p.cfg.Tire.Id = p._idTire.Hex()
			p.cfg.Save()
			time.Sleep(time.Second * 10)
		}
	}
}

func dateTimeToUTC(d int, t int) string {
	year := d/10000 + 2000
	month := d % 10000 / 100
	day := d % 100
	hour := t / 10000
	min := t % 10000 / 100
	sec := t % 100

	ret := fmt.Sprintf("%d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, min, sec)
	return ret
}

type Result struct {
	Code *int   `json:"code"`
	Msg  string `json:"msg"`
}

func (p *Processor) Post(url string, _id bson.ObjectId, v interface{}) error {
	buf, _ := json.Marshal(v)
	s := string(buf)
	zLog("id:", _id.Hex(), " 推送：", s)

	req, _ := http.NewRequest("POST", url, bytes.NewBuffer(buf))
	req.Header.Add("Content-Type", "application/json")

	for _, v := range p.cfg.HttpHeaders {
		if v.Key != "" && v.Value != "" {
			req.Header.Add(v.Key, v.Value)
		}
	}

	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	client := &http.Client{Transport: tr}
	resp, err := client.Do(req)
	if err != nil {
		zLogError("id:", _id.Hex(), "推送失败：", err.Error())
		return err
	}

	defer resp.Body.Close()
	body, _ := ioutil.ReadAll(resp.Body)
	zLog("id:", _id.Hex(), "返回结果：", string(body))

	var res Result
	err = json.Unmarshal(body, &res)
	if err == nil {

		if res.Code == nil {
			err = errors.New("无效的返回内容")
		} else {

			if *res.Code != p.cfg.Failed.SuccessFlag {
				err = errors.New(res.Msg)
			}
		}
	}

	return err
}

type GPSDataSendT struct {
	SN            string  `json:"SN"`
	Type          int     `json:"Type"`
	DeviceUTCTime string  `json:"Time"`
	Latitude      float64 `json:"Lat"`
	Longitude     float64 `json:"Lng"`
	Speed         float64 `json:"Speed"`
	Course        int     `json:"Dir"`
	DeviceBattery float64 `json:"Pow"`
	PowerSource   int     `json:"Ps"`
	GSM           int     `json:"GSM"`
	GPS           int     `json:"GPS"`
}

type GPSDataAddT struct {
	PowerSource int     `bson:"ps"`
	Vol         float64 `bson:"vol"`
	GSM         int     `bson:"gsmss"`
	GPS         int     `bson:"gpssc"`
}
type GPSDataT struct {
	Id        bson.ObjectId `bson:"_id"`
	Date      int           `bson:"date"`
	Time      int           `bson:"time"`
	SN        string        `bson:"rid"`
	Latitude  float64       `bson:"lat"`
	Longitude float64       `bson:"lng"`
	Speed     float64       `bson:"speed"`
	Course    int           `bson:"dir"`
	Add       GPSDataAddT   `bson:"add"`
}

func (p *GPSDataSendT) fromData(data GPSDataT) {
	p.SN = data.SN
	p.DeviceUTCTime = dateTimeToUTC(data.Date, data.Time)
	p.Latitude = data.Latitude
	p.Longitude = data.Longitude
	p.Speed = data.Speed
	p.Course = data.Course
	p.DeviceBattery = data.Add.Vol
	p.PowerSource = data.Add.PowerSource
	p.GSM = data.Add.GSM
	p.GPS = data.Add.GPS
}

type SingleTireDataSendT struct {
	SensorID      string  `json:"ID"`
	TirePressure  float32 `json:"Press"`
	Temperature   int     `json:"temp"`
	SensorBattery float32 `json:"Pow"`
	WheelPlace    string  `json:"Pos"`
	TireStatus    string  `json:"Stat"`
}

type TireDataSendT struct {
	SN            string                 `json:"SN"`
	Type          int                    `json:"Type"`
	DeviceUTCTime string                 `json:"Time"`
	Latitude      float64                `json:"Lat"`
	Longitude     float64                `json:"Lng"`
	Wheels        []*SingleTireDataSendT `json:"Sensors"`
}

func (p *TireDataSendT) fromTireRawData(data *TireDataRawT, needSensorLost, fixData bool, maxTemperature int,
	errorPressureRange int, lastData *LastData) {
	p.SN = data.Rid
	p.DeviceUTCTime = dateTimeToUTC(data.Date, data.Time)
	p.Latitude = data.Latitude
	p.Longitude = data.Longitude

	if p.Wheels != nil {
		p.Wheels = nil
	}
	p.Wheels = make([]*SingleTireDataSendT, 0)

	var ld *LastDataT
	var pressureDValue float32

	for _, v := range data.Tires {

		if ((v.Temperature == 0 && v.Pressure == 0) || (v.Temperature > maxTemperature)) &&
			v.NoData != 1 && fixData {

			v.NoData = 1
		}

		if v.NoData == 1 {
			v.Temperature, v.Pressure = 0, 0
		}
		stds := p.fromTireData(&v, needSensorLost)

		if stds != nil {

			if lastData.warningRepeatTimes > 0 || lastData.errorPressureRepeatTimes > 0 {
				ld = lastData.GetData(p.SN, v.CardId, v.SerialNum)

				if stds.TireStatus == "160" {

					ld.WarningRepeatTimes = 0

					if v.Temperature == 205 {
						ld.LastPressure.Temperature = 0
					} else {
						ld.LastPressure.Temperature = v.Temperature
					}

					if ld.LastPressure.Pressure == 0.0 {
						ld.LastPressure.Pressure = v.Pressure
					} else {
						pressureDValue = ((v.Pressure - ld.LastPressure.Pressure) * 100) / ld.LastPressure.Pressure
						if pressureDValue < 0 {
							pressureDValue = -pressureDValue
						}
						if pressureDValue < float32(errorPressureRange) {

							ld.LastPressure.Pressure = v.Pressure
							ld.LastPressure.ErrorPressureRepeatTimes = 0
						} else {

							if ld.LastPressure.Pressure != 0.0 &&
								ld.LastPressure.ErrorPressureRepeatTimes < lastData.errorPressureRepeatTimes {

								ld.LastPressure.ErrorPressureRepeatTimes++

								stds.TirePressure = ld.LastPressure.Pressure
							}
						}
					}
				} else {

					if ld.LastPressure.Pressure != 0.0 && ld.LastPressure.Temperature != 0 &&
						ld.WarningRepeatTimes < lastData.warningRepeatTimes {

						stds.TirePressure = ld.LastPressure.Pressure
						stds.Temperature = ld.LastPressure.Temperature

						stds.TireStatus = "160"

						ld.WarningRepeatTimes++
					}
				}
			}

			p.Wheels = append(p.Wheels, stds)
		}
	}
}

func (p *TireDataSendT) fromTireData(data *TireDataT, needSensorLost bool) *SingleTireDataSendT {

	if (data.Temperature > 155 || data.Pressure > 15.5) && (data.NoData != 1) {
		return nil
	}

	stds := SingleTireDataSendT{}

	stds.SensorID = data.SensorId
	stds.SensorBattery = data.Voltage
	stds.Temperature = data.Temperature
	stds.TirePressure = data.Pressure
	stds.WheelPlace = fmt.Sprintf("%d-%d", data.CardId, data.SerialNum)
	var status []string = make([]string, 0)

	if data.PressureStatus == 1 {

		status = append(status, "161")
	}
	if data.PressureHighOrLow == 1 {

		status = append(status, "165")
	} else if data.PressureHighOrLow == 2 {

		status = append(status, "163")
	}
	if data.TemperatureStatus == 1 {

		status = append(status, "166")
	}
	if data.NoData == 1 && needSensorLost {

		status = append(status, "168")
	}
	if len(status) == 0 {
		stds.TireStatus = "160"
	} else {
		stds.TireStatus = strings.Join(status, ",")
	}

	return &stds
}
